﻿using AiMachineControl.Common;
using AiMachineControl.IService;
using AiMachineControl.Model;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using System.Buffers;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;

namespace AiMachineControl.Service
{
    public class HostedService : IHostedService
    {
        private Timer _timer;
        private Timer _timer2;
        private readonly IServiceScopeFactory _serviceScopeFactory;
        private readonly IHostApplicationLifetime _hostApplicationLifetime;
        private readonly IAppsettingService _appsettingService;
        private readonly ICommandProcessService _commandService;
        private readonly IFileService _fileService;
        private readonly IFileProvider _fileProvider;
        private readonly GlobalService _globalService;
        public static ClientWebSocket webSocket;
        public string SoketServerUrl;
        public static string ClientId;
        public HostedService(IServiceScopeFactory serviceScopeFactory, IHostApplicationLifetime hostApplicationLifetime, IAppsettingService appsettingService, ICommandProcessService commandService, IFileService fileService, IFileProvider fileProvider, GlobalService globalService)
        {
            _serviceScopeFactory = serviceScopeFactory;
            _hostApplicationLifetime = hostApplicationLifetime;
            _appsettingService = appsettingService;
            _commandService = commandService;
            _fileService = fileService;
            _fileProvider = fileProvider;
            _globalService = globalService;
        }

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            //开始执行任务
            SoketServerUrl = _appsettingService.GetAppsetting("SoketServerUrl");
            string UpdateTime = _appsettingService.GetValue(new string[] { "MachineInfo", "UpdateTime" });
            string IsRuning = _appsettingService.GetValue(new string[] { "MachineInfo", "IsRuning" });
            ClientId = _appsettingService.GetValue(new string[] { "MachineInfo", "Id" });
            CacheHelper.Set_NotExpire("AiUpdate", UpdateTime);
            CacheHelper.Set_NotExpire("MachineRuning", IsRuning);
            CacheHelper.Set_NotExpire("LastSoketTime", DateTime.Now);
            webSocket = await CreateAsync(SoketServerUrl);
            _timer = new Timer(StartHeartBeatTask, null, TimeSpan.Zero, TimeSpan.FromSeconds(30));//每30秒钟执行一次
            _timer2 = new Timer(UpdateMachineFiles, null, TimeSpan.Zero, TimeSpan.FromHours(12));//每12小时执行
        }

        private async void StartHeartBeatTask(object state)
        {
            await SendHeartBeatAsync();
        }
        public async Task SendHeartBeatAsync()
        {
            if (webSocket != null && webSocket.State == WebSocketState.Open)
            {
                string ClientID = _appsettingService.GetValue(new string[] { "MachineInfo", "Id" });
                //按时发送心跳包
                SoketMessage sendSoket = new SoketMessage()
                {
                    SendClientId = ClientID,
                    Action = (int)EnumModel.ActionTypeEnum.HertNotice,
                    StatusCode = 0,
                    Message = "hert bag",
                    Data = DateTime.Now.ToString()
                };
                var sendStr = Encoding.UTF8.GetBytes(JsonHelper.ToJson(sendSoket));
                await webSocket.SendAsync(sendStr, WebSocketMessageType.Text, true, CancellationToken.None);
                Console.WriteLine("***Send heart bag is success...");
            }
            else
            {
                Console.WriteLine("***WebSocket is closed，connecting...");
                WSocketClient_OnClose(webSocket, SoketServerUrl);
            }
        }

        public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;

        public async void OpenSoketConnect(string serverUrl)
        {
            //全部消息容器
            List<byte> bs = new List<byte>();
            var buffer = ArrayPool<byte>.Shared.Rent(1024);
            try
            {
                while (webSocket != null && webSocket.State == WebSocketState.Open)
                {
                    var result = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
                    bs.AddRange(buffer.Take(result.Count));
                    //消息是否已接收完全
                    if (result.EndOfMessage)
                    {
                        //发送过来的消息
                        string receiveText = Encoding.UTF8.GetString(buffer.AsSpan(0, result.Count));
                        WSocketClient_OnMessage(webSocket, receiveText);//处理消息
                        bs = new List<byte>();//清空消息容器
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine("Soket连接错误:" + ex.Message+",尝试重连");
                webSocket = await CreateAsync(SoketServerUrl);
            }
        }

        /// <summary>
        /// 创建Soket客户端实例
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<ClientWebSocket> CreateAsync(string ServerUri)
        {
            try
            {
                webSocket = new ClientWebSocket();
                webSocket.Options.RemoteCertificateValidationCallback = delegate { return true; };

                await webSocket.ConnectAsync(new Uri(ServerUri), CancellationToken.None);
                if (webSocket.State == WebSocketState.Open)
                {
                    OpenSoketConnect(ServerUri);
                    return webSocket;
                }
            }
            catch (Exception ex)
            {
                return null;
            }
            return null;
        }

        /// <summary>
        /// Soket链接状态关闭时，重新链接服务端
        /// </summary>
        /// <param name="client"></param>
        /// <param name="ServerUri"></param>
        private async void WSocketClient_OnClose(ClientWebSocket client, string ServerUri)
        {
            //每次重连前都模拟请求网站服务端，防止因回收造成的拒绝连接
            string webServerUrl = _appsettingService.GetAppsetting("WebServerUrl");
            string aa = RequestHelper.Post(webServerUrl + "/Api/Interface/Login/1", "{\"UserName\":\"zhangsan\",\"Password\":\"\"}", "application/x-www-form-urlencoded");
            webSocket = await CreateAsync(ServerUri);
        }

        /// <summary>
        /// 处理服务端发送过来的消息
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="data"></param>
        private void WSocketClient_OnMessage(object sender, string data)
        {
            //处理的消息错误将会忽略
            try
            {
                //如果不是当前客户端，不做处理
                string ClientID = _appsettingService.GetValue(new string[] { "MachineInfo", "Id" });
                //根据通讯信息作出对应的操作
                SoketMessage soketMsg = JsonHelper.ToObject<SoketMessage>(data);
                if (soketMsg != null && ClientID == soketMsg.SendClientId)
                {
                    if (soketMsg.StatusCode != (int)EnumModel.ActionTypeEnum.HertNotice)
                    {
                        Console.WriteLine("Accept Task =>>：" + data);
                    }
                    else {
                        Console.WriteLine("Heartbeat packet sent successfully----" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
                    }
                    string IsRepairRuning = CacheHelper.Get<string>("MachineRuning");
                    if (IsRepairRuning == "True")
                    {
                        //机器在维护中，暂不能提供服务
                        SoketMessage sendSoket = new SoketMessage()
                        {
                            SendClientId = ClientID,
                            Action = (int)EnumModel.ActionTypeEnum.MachineRepair,
                            StatusCode = 0,
                            Message = "当前算力机正在维护，无法使用",
                            Data = DateTime.Now.ToString()
                        };
                        string jsonsocket = JsonSerializer.Serialize(sendSoket!,MetadataOnlySoketMessageOnlyContext.Default.SoketMessage);
                        Send(jsonsocket);
                        Console.WriteLine("算力机使用/维护中");
                    }
                    SoketMessage sendSoketMsg;
                    switch (soketMsg.StatusCode)
                    {
                        case (int)EnumModel.MachineStatusEnum.VideoCombine:
                            VideoCombine combinfo = JsonSerializer.Deserialize<VideoCombine>(soketMsg.Data.ToString(), MetadataOnlyVideoCombineOnlyContext.Default.VideoCombine);
                            if (combinfo != null)
                            {
                                string aiSoftDosArr = _appsettingService.GetValue(new string[] { "PythonCmd", "Combine" });
                                _commandService.CombineVideo(soketMsg.UseLogId, combinfo.proName, combinfo, aiSoftDosArr);
                            }

                            break;
                        case (int)EnumModel.MachineStatusEnum.SplitVoice:
                            VoiceInfo _Voice = JsonSerializer.Deserialize<VoiceInfo>(soketMsg.Data.ToString(), MetadataOnlyVoiceInfoOnlyContext.Default.VoiceInfo);
                            if (_Voice != null)
                            {
                                string aiSoftDosArr = _appsettingService.GetValue(new string[] { "PythonCmd", "SplitWav" });
                                _commandService.SpliteWavs(soketMsg.UseLogId,_Voice.Id, _Voice.ModelName, _Voice.FileAdd, aiSoftDosArr);
                            }
                           
                            break;
                        case (int)EnumModel.MachineStatusEnum.Reading://准备
                            AiProjectInfo aiproject = JsonSerializer.Deserialize<AiProjectInfo>(soketMsg.Data.ToString(), MetadataOnlyAiProjectInfoOnlyContext.Default.AiProjectInfo);
                            bool IsSuccess = false;
                            string Message = "本地软件已启动运行";
                            if (aiproject != null)
                            {
                                ///是否离线渲染
                                if (aiproject.IsOffline)
                                {
                                    OffLineInfo offline = aiproject.OffLineInfo;
                                    string aiSoftDosArr="";
                                    switch (offline.Type)
                                    {
                                        //和本地文件匹配，如果一致，直接启动，不一致先下载最新的模型文件至本地，再启动
                                        case (int)EnumModel.MachineTypeEnum.AiVoice://换声音
                                            //aiSoftDosArr = _appsettingService.GetAppsetting("AiVoice");
                                            break;
                                        case (int)EnumModel.MachineTypeEnum.AiPerson://离线视频渲染
                                            aiSoftDosArr = _appsettingService.GetValue(new string[] { "PythonCmd", "Offline" });
                                            break;
                                        default:
                                            //aiSoftDosArr = _appsettingService.GetAppsetting("ChatGPT");
                                            break;
                                    }
                                    IsSuccess = _commandService.StartOfflineProject(offline.Type== (int)EnumModel.MachineTypeEnum.AiVoice ?"voice":"video",soketMsg.UseLogId, aiproject.OffLineInfo, aiSoftDosArr,out Message);
                                }
                                else
                                {
                                    Console.WriteLine("开始在线实时推理");
                                    string aiSoftDosArr = "";
                                    switch (aiproject.Type)
                                    {
                                        //case (int)EnumModel.MachineTypeEnum.AiVoice://换声音
                                        //    aiSoftDosArr = _appsettingService.GetAppsetting("AiVoice");
                                        //    break;
                                        //case (int)EnumModel.MachineTypeEnum.AiFace://换脸
                                        //    aiSoftDosArr = _appsettingService.GetAppsetting("AiFace");
                                        //    break;
                                        case (int)EnumModel.MachineTypeEnum.AiPerson://数字人直播
                                            aiSoftDosArr = _appsettingService.GetValue(new string[] { "PythonCmd", "Online" });
                                            break;
                                        default:
                                            //aiSoftDosArr = _appsettingService.GetAppsetting("ChatGPT");
                                            break;
                                    }
                                    IsSuccess = _commandService.ReadingProjectInfo(soketMsg.UseLogId,aiproject.Type, aiproject.ProName, aiproject.VersionCode, aiproject.IsFirst, aiSoftDosArr,out Message);
                                }
                            }
                            Console.WriteLine("任务执行返回值:"+ Message);
                            break;
                        case (int)EnumModel.MachineStatusEnum.Ending://结束
                            _globalService.myGlobalPsi.Kill();//关闭当前进程
                            sendSoketMsg = new SoketMessage()
                            {
                                SendClientId = ClientID,
                                Action = (int)EnumModel.ActionTypeEnum.MachineClose,
                                StatusCode = 0,
                                Message = "请将当前算力机设置为空闲状态",
                                Data = DateTime.Now.ToString()
                            };
                            Send(JsonSerializer.Serialize(sendSoketMsg!, MetadataOnlySoketMessageOnlyContext.Default.SoketMessage));
                            break;
                        case (int)EnumModel.MachineStatusEnum.Updating://指定项目更新
                            //需要的项目类型
                            AiProjectInfo updateProject = JsonHelper.ToObject<AiProjectInfo>(soketMsg.Data.ToString());
                            bool IsUpdateSuccess = _commandService.UpdateProjectInfo(updateProject.Type, updateProject.ProName, updateProject.VersionCode);
                            sendSoketMsg = new SoketMessage()
                            {
                                SendClientId = ClientID,
                                Action = (int)EnumModel.ActionTypeEnum.MachineRepairEnd,
                                StatusCode = 0,
                                Message = "当前项目更新完毕",
                                Data = DateTime.Now.ToString()
                            };
                            string jsonsockets = JsonSerializer.Serialize(sendSoketMsg!, MetadataOnlySoketMessageOnlyContext.Default.SoketMessage);
                            Send(jsonsockets);
                            break;
                        default://心跳包检测
                            DateTime lastsokettime = CacheHelper.Get<DateTime>("LastSoketTime");
                            if ((DateTime.Now - lastsokettime).Seconds > 90)
                            {
                                //超过90秒未收到服务器通知，就释放当前链接并建立新链接
                                WSocketClient_OnClose(webSocket, SoketServerUrl);
                            }
                            else
                            {
                                CacheHelper.Set_NotExpire("LastSoketTime", DateTime.Now);
                            }
                            break;
                    }
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message.ToString());
            }

        }

        /// <summary>
        /// 使用连接发送文本消息
        /// </summary>
        /// <param name="ws"></param>
        /// <param name="mess"></param>
        /// <returns>是否尝试了发送</returns>
        public bool Send(string mess)
        {
            if (webSocket == null || webSocket.State != WebSocketState.Open)
                return false;
            Task.Run(async () =>
            {
                var replyMess = Encoding.UTF8.GetBytes(mess);
                //发送消息
                await webSocket.SendAsync(new ArraySegment<byte>(replyMess), WebSocketMessageType.Text, true, CancellationToken.None);
            });
            Console.WriteLine("Send message to server:" + mess);
            return true;
        }

        /// <summary>
        /// 使用连接发送文本消息
        /// </summary>
        /// <param name="ws"></param>
        /// <param name="mess"></param>
        /// <returns>是否尝试了发送</returns>
        public static bool SendSocketMsg(SoketMessage sendSoketMsg)
        {
            Console.WriteLine("==================Task Over=============================");
            sendSoketMsg.SendClientId = ClientId;
            string mess = JsonHelper.ToJson(sendSoketMsg);
            if (webSocket == null || webSocket.State != WebSocketState.Open)
                return false;
            Task.Run(async () =>
            {
                var replyMess = Encoding.UTF8.GetBytes(mess);
                //发送消息
                await webSocket.SendAsync(new ArraySegment<byte>(replyMess), WebSocketMessageType.Text, true, CancellationToken.None);
            });
            Console.WriteLine("Send message to server:" + mess);
            Console.WriteLine("=======================================================");
            return true;
        }

        /// <summary>
        /// 定时更新本机模型文件
        /// </summary>
        /// <param name="state"></param>
        private void UpdateMachineFiles(object state)
        {
            using (var scope = _serviceScopeFactory.CreateScope())
            {
                //通过接口查询模型服务器所有的模型项目
                string AiModelServerUrl = _appsettingService.GetAppsetting("AiModelServerUrl");//模型文件服务器地址
                string AiModelUpdateInterfaceUrl = _appsettingService.GetAppsetting("AiModelUpdateInterfaceUrl");//模型文件更新检测接口地址
                string UpdateTime = CacheHelper.Get<string>("AiUpdate");
                string IsRuning = CacheHelper.Get<string>("MachineRuning");
                 string ClientID = _appsettingService.GetValue(new string[] { "MachineInfo", "Id" });
                if (IsRuning == "False" || IsRuning == "0")//非空闲状态
                {
                    if (UpdateTime != DateTime.Now.ToString("yyyy-MM-dd"))//最后一次更新时间不是今天
                    {   
                        //检测并更新本地所有项目模型
                        string AiModelFolder = _appsettingService.GetAppsetting("AiModelFolder");//模型文件本地保存文件夹地址
                        var directory = new DirectoryInfo(AiModelFolder);
                        DirectoryInfo[] dir = directory.GetDirectories();
                        if (dir.Count() > 0)//本机存在项目
                        {
                            //开始更新时，将算力机设置为维护状态
                            CacheHelper.Set_NotExpire("MachineRuning", "True");
                            //机器在维护中，暂不能提供服务
                            SoketMessage sendSoket = new SoketMessage()
                            {
                                SendClientId = ClientID,
                                Action = (int)EnumModel.ActionTypeEnum.MachineRepair,
                                StatusCode = 0,
                                Message = "当前算力机正在维护，无法使用",
                                Data = DateTime.Now.ToString()
                            };
                            string jsonsockets = JsonSerializer.Serialize(sendSoket!, MetadataOnlySoketMessageOnlyContext.Default.SoketMessage);
                            Send(jsonsockets);
                            foreach (DirectoryInfo file in dir) 
                            {
                                string filePath = AiModelFolder + "/" + file.Name + "/" + "version.txt";
                                // 读取文件内容  
                                string[] lines = File.ReadAllLines(filePath);
                                // 修改文件中的值  
                                Console.WriteLine(file.Name);
                                int AiType = (int)EnumModel.MachineTypeEnum.AiPerson; 
                                string proName = file.Name;
                                string VersionNo = lines[0].Replace("version", "");
                                string newSaveUrl = AiModelFolder;
                                //通过接口获取更新文件下载地址
                                string returnval = RequestHelper.Get(AiModelServerUrl + AiModelUpdateInterfaceUrl + "?type=" + AiType + "&proName=" + proName + "&versionno=" + VersionNo);
                                if (string.IsNullOrEmpty(returnval))
                                    continue;
                                string[] ravals = returnval.Split("@@");
                                InterfaceMachine machine = new InterfaceMachine();
                                if (ravals.Length > 1)
                                {
                                    machine.Version = ravals[0].Replace("\"", "");
                                    machine.FileAddress = ravals[1].Replace("\"", "");
                                }
                                else
                                {
                                    continue;
                                }
                                if (!string.IsNullOrEmpty(machine.FileAddress))
                                {
                                    string newFileName = "";
                                    string fileAdress = _fileService.FileDownSave(machine.FileAddress, newSaveUrl, newFileName);
                                    newSaveUrl = string.Format("{0}/{1}", newSaveUrl, newFileName);
                                    string filename = FileHelper.GetFileName(fileAdress);
                                    //如果上传的文件为压缩文件，就解压缩
                                    if (FileHelper.GetFileFullExt(fileAdress) == ".zip" || FileHelper.GetFileFullExt(fileAdress) == ".tar" || FileHelper.GetFileFullExt(fileAdress) == ".7z")
                                    {
                                        FileHelper.UnZip(newSaveUrl, fileAdress);
                                        //解压后删除下载的压缩文件
                                        FileHelper.DeleteFile(newSaveUrl + "/" + filename, true);
                                    }
                                }

                                lines[0] = "version" + machine.Version;
                                // 将修改后的内容写回文件  
                                File.WriteAllLines(filePath, lines);
                            }
                            //更新完毕后设置最新更新时间
                            CacheHelper.Set_NotExpire("MachineRuning", "False");
                            CacheHelper.Set_NotExpire("AiUpdate", DateTime.Now.ToString("yyyyMMdd"));
                            //机器在维护中，暂不能提供服务
                            sendSoket = new SoketMessage()
                            {
                                SendClientId = ClientID,
                                Action = (int)EnumModel.ActionTypeEnum.MachineRepairEnd,
                                StatusCode = 0,
                                Message = "当前算力机维护完毕，可正常使用",
                                Data = DateTime.Now.ToString()
                            };
                            jsonsockets = JsonSerializer.Serialize(sendSoket!, MetadataOnlySoketMessageOnlyContext.Default.SoketMessage);
                            Send(jsonsockets);
                        }
                    }
                }
                Console.WriteLine("The Background Timer Service has normal operation...");
            }
        }

        
    }
}
